#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <net/if.h>

int create_raw_multi_if(const char *group, int service, struct sockaddr_storage *addr, int proto)
{
	int fd = socket(AF_INET6, SOCK_DGRAM, proto);
	if (fd < 0) {
		perror("socket");
		exit(0);
	}

	struct ipv6_mreq mreq;
	mreq.ipv6mr_interface = if_nametoindex("eth0");
	if (inet_pton(AF_INET6, group, &mreq.ipv6mr_multiaddr) != 1) {
		perror("inet_pton");
		exit(0);
	}

	if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) {
		perror("setsockopt");
		exit(0);
	}

	int on = 1;
	if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) < 0) {
		perror("setsockopt");
		exit(0);
	}

	memset(addr, 0, sizeof(struct sockaddr_storage));
	struct sockaddr_in6 *maddr = (void *)addr;
	maddr->sin6_family = AF_INET6;
	maddr->sin6_port = htons(service);
	maddr->sin6_addr = in6addr_any;
	if (bind(fd, (void *)addr, sizeof(struct sockaddr_storage)) < 0) {
		perror("bind");
		exit(0);
	}

	return fd;
}

void analysis_cmsg(struct msghdr *msg)
{
	struct cmsghdr *cmsg;
	char ifname[IF_NAMESIZE], ip[INET6_ADDRSTRLEN];
	for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
		if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
			struct in6_pktinfo *pkt = (void *)CMSG_DATA(cmsg);
			inet_ntop(AF_INET6, &pkt->ipi6_addr, ip, sizeof(ip));
			printf("recv_if=%s dst=%s\n", if_indextoname(pkt->ipi6_ifindex, ifname), ip);
		}
	}
}

int main()
{
	struct sockaddr_storage addr;
	int fd = create_raw_multi_if("ff02::8888", 8888, (void *)&addr, IPPROTO_UDP);
	char buf[1024];
	int len = 0;
	int ret = 0;

	struct sockaddr_storage peer;
	struct iovec iov = { buf, sizeof(buf) };
	char host[INET6_ADDRSTRLEN], serv[8], control[1024];
	struct msghdr msg = { &peer, sizeof(peer), &iov, 1, control, sizeof(control), 0 };
	for (;;) {
		if ((len = recvmsg(fd, &msg, 0)) < 0) {
			perror("recvmsg");
			exit(0);
		}
		ret = getnameinfo((void *)&peer, sizeof(peer), host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV);
		if (ret) {
			perror(gai_strerror(ret));
		}
		buf[len] = 0;
		printf("recv=%s from=%s:%s ", buf, host, serv);
		analysis_cmsg(&msg);
	}

	return 0;
}